Camada C4 do estudo WebTestPilot · v2.2 · corte 27 ago 2026 Público: quem vai implementar. Critério de qualidade: um engenheiro consegue começar amanhã — e o documento diz o que ele vai descobrir na primeira semana. Fecha decisões (ADR-01 a ADR-16) e separa o que está decidido do que só se resolve medindo (§ 12). Alvo: AWS, com escalabilidade. Aplicação de referência: Vendure +
nextjs-starter-vendure; troca-se por app real pelo checklist do § 9.
0 · Como ler
- Fechado = decisão com evidência N4 (doc oficial, código-fonte, preço datado) e alternativas rejeitadas registradas. Muda só por ADR nova.
- Aberto = depende de medição; o experimento está no § 12.
- Preços e limites AWS lidos em 27/08/2026; citações literais em
pesquisa/exploracao/X4-aws-plataforma.mdepesquisa/revisoes/v2/R3-plataforma-aws.md. - Toda MR carrega uma classe de custo: K0 (sem estado), K1 (par com estado,
contexto próprio), K2 (fan-out) — definidas no C2 § 4. MRs são sempre citadas com
prefixo (
MR-A1); "C1"–"C4" sem prefixo é sempre documento. - A medida que importa num deploy AWS é onde os dados ficam e por onde os bytes passam: nesta stack, nada sai da conta — nem sessão autenticada, nem banco, nem navegador — e a rede é uma ADR (ADR-15), porque sem ela o egress de imagem custaria mais que o compute (§ 7.3).
1 · Quatro princípios
- O veredito é código; o agente descobre e propõe; o LLM tria depois. Não é diferencial deste estudo — é consenso operacional em 2026 (Octomind 2025; WebTestPilot FSE 2026). O que distingue a família D é a origem do predicado: relação entre execuções, não spec inferida. É isso que o piloto mede (E9c — com MR validada em E2E web, que fração das violações é bug?). A regra "o veredito é código" está no C2 § 7.3. [protocolo · A20]
- A relação é fixada antes de executar e falsificada por mutante. Operador, direção,
observáveis, guardas e exclusões ficam em arquivo (
anchoredOn) antes da primeira execução; mudam só comrevisionNotes; e cada MR tem ao menos um mutante que a viola (falsifiedBy), executado por ferramenta de mutação onde há código. MRs são propriedades necessárias, não suficientes; o oráculo que não pode falhar é o que edita a relação depois do diff. (C2 § 4 regra 2, § 6.5.) - O par fonte/seguidor nunca se divide. Dois browser contexts no mesmo processo, um
único
test(), um único trace, o mesmo clock, o mesmo banco. Orquestração faz fan-out de MRs; nunca parte uma MR ao meio. [protocolo · A27] - Falha barulhenta é melhor que replanejamento silencioso. Nada na stack "conserta" um passo em tempo de execução: nem healer, nem cache de agente, nem Runtime Recovery. Falha de infraestrutura é ruído no passo 0 — com limiar calibrado, não chutado — e nunca entra como violação.
2 · A stack, camada a camada — decisões fechadas
| Camada | Decisão | ADR | O que ficou de fora |
|---|---|---|---|
| 1 · Driver | Playwright 1.62.1, biblioteca em processo; imagem própria só-Chromium derivada de mcr.microsoft.com/playwright:v1.62.1-noble, no ECR privado com índice SOCI v2 |
ADR-10 | Cypress; Selenium; latest |
| 1 · Infra de navegador | Dentro da conta: ECS Fargate arm64 para K1/K2 (com a app sob teste dentro da task), Lambda para K0 | ADR-01, 06 | Browserbase/Steel/Anchor (sessão sai da VPC); browserless (SSPL); grids por slot; Device Farm |
| 1 · Rede | Subnets privadas + VPC endpoints (ecr.api, ecr.dkr, logs, secretsmanager) + S3 gateway; sem NAT; SG sem inbound | ADR-15 | NAT (US$ 20/rodada de egress de imagem); IP público no runner |
| 2 · Determinismo | page.clock (install antes de qualquer chamada de clock) · routeFromHAR só para assets/terceiros, nunca para a API da app · serviceWorkers: 'block' declarado · storageState por sujeito, credentials para passkey · clipboard e viewport fixos |
ADR-11 | Gravar a API no HAR |
| 2 · Estado K1 | Postgres sidecar na mesma task, PGDATA explícito fora do VOLUME, listen_addresses=localhost, ownership assada no build |
ADR-03 | Aurora clone por par; Neon; Testcontainers; bind mount de task; trust em all |
| 2 · Estado K2 e shrinking | Um database por par via CREATE DATABASE … TEMPLATE num cluster Aurora Serverless v2 PG 17.x (≥ 17.4), min ACU 4 na rodada e 0 fora, criado por init container antes da app subir |
ADR-04 | PG 18 no Serverless v2 (não documentado); sidecar para K2; auto-pause na rodada; IAM DB auth |
| 3 · Motor | fast-check 4.9 para sequências e fixtures (entityGraph), scheduledModelRun para MR-B2, shrinking offline; a relação é código próprio. Bombadil avaliado como motor das MRs K0 de sessão única (P ∩ D) |
ADR-12 | Chamar fast-check de "motor de relação"; shrinking no CI; gemtest |
| 4 · Geração | Molde travado → LLM instancia com saída Zod → baseline → falsificação por mutante → promoção. A contribuição é a automação do filtro, não o filtro | ADR-13 (geração e agente) | Família F; R(·) escolhida após execução |
| 5 · Projeção | Locator tipado como padrão; ARIA snapshot com filtro; rede só como cross-check; varredura implícita em toda execução; herda a literatura de abstração de estado de GUI | ADR-14 | Screenshot como R(·); extract; resposta de API como projeção primária |
| 6 · Execução | Playwright runner; par = um test() com test.step() e anotação mr:<id>; retryStrategy: 'isolated'; trace: 'retain-on-failure'; contrato de exit code |
ADR-08 | Fonte e seguidor como testes separados; trace: 'on' |
| 6 · Orquestração | Step Functions Standard + Distributed Map (filhos Standard), ponteiro S3, Retry em States.TaskFailed/AmazonECS.Unknown, TimeoutSeconds, semáforo de rodadas |
ADR-05 | Express; Map inline; dividir o par; rodadas concorrentes sem semáforo |
| 6 · Monitoramento | CloudWatch Synthetics para K0 em produção; K0 em Lambda no pré-deploy na versão do runtime, adaptador runPair com dois entrypoints, job de acompanhamento de runtime com dono |
ADR-06 | Checkly no alvo AWS |
| 6 · Observabilidade | Histórico determinístico por MR antes de LLM; controle de ruído fonte×fonte na mesma task; tags de custo por run-id/owner/mr-id |
ADR-09 | Quarentena automática; Container Insights por padrão |
| Agente | Test Agents planner + generator (dev-time, transporte MCP), healer removido; Stagehand só para descoberta em runtime | ADR-13 (geração e agente) | Healer; extract em R(·) |
| Segurança e IAM | Segredos via Secrets Manager no container definition; 4 roles mínimas; bucket de traces criptografado com retenção e acesso restrito (trace + storageState = sessão reproduzível) |
ADR-16 | trust em rede; traces públicos na conta |
3 · O laço de julgamento
0. SANIDADE exit code do browser · crash de renderer · sidecar HEALTHY · app HEALTHY
· delta de latência fonte×seguidor acima do LIMIAR CALIBRADO (§ 12)
→ RUÍDO, sai
1. SELEÇÃO classe de custo (K0/K1/K2) × módulo tocado (owner) × orçamento
(Reporter.preprocess) → manifesto S3
2a. DETERMINISMO in-process, microssegundos: clock.install(T0) · routeFromHAR(assets)
· serviceWorkers:'block' · storageState(sujeito) · viewport
2b. ESTADO a única parte cara — antes de a app subir:
(K1) sidecar pg_isready · (K2) init container: CREATE DATABASE … TEMPLATE
2c. APP vendure-server + vendure-worker + storefront sobem apontando para o DB
3. EXECUÇÃO context A = FONTE, context B = SEGUIDOR, mesmo processo, um test()
3b. ASSENTAMENTO após mutação (server action, POST): esperar a resposta da ação e
projetar até duas leituras consecutivas coincidirem (settle: projeta,
1 s, reload, projeta; OracleSetupError após 6) — a app tem dois
estados por ~1–2 s (cache de RSC/`updateTag`); a MR lê R(·) uma vez,
a asserção convencional esconde isso com auto-wait. Medido na app de
referência (§ 10.3): 4/4 falsos positivos em MR-B1 sem este passo,
0/5 com ele.
4. PROJEÇÃO R(fonte), R(seguidor) por locator tipado / ARIA filtrado;
OracleSetupError se contador≠lista, paginação>guarda, observável vazio,
ou projeção não assentou
5. RELAÇÃO função pura sobre as duas projeções · veredito determinístico
6. REGISTRO mr.evaluated += 1 (EMF) · verdict.json → S3 (sempre) · se violar:
seed + replayPath + trace.zip → S3 · exit 0 ("o harness rodou")
NÃO encolhe aqui
7. TRIAGEM (a′) rerun automático com settle e reload — barato, e na primeira
semana não há histórico
(a) histórico determinístico da MR (flake? primeira vez?)
(b) controle de ruído: divergência fonte×fonte da mesma build/task
(não detecta estado não assentado — fonte vazia é sempre igual)
(c) só então LLM: {bug, MR mal especificada, ruído}, com evidência
—. SHRINKING offline, sob demanda, com orçamento, relendo seed+replayPath;
exige estado reconstruível → database por par (ADR-04); leitura:
Jiang, Tian & Chen (2026), delta debugging através de MT
Contrato de exit code (ADR-08): 0 = o harness rodou e o veredito está em S3
(inclusive violação); ≠ 0 = ruído de infra → Retry no filho. Sem isso o .sync do
Step Functions só sabe dizer States.TaskFailed.
4 · Formato de MR
Uma MR é um arquivo versionado, revisável em PR, sem LLM no laço depois de promovida.
Nove campos obrigatórios: id, owner, costClass, anchoredOn, projection,
source, follower(s), relation, falsifiedBy. O resto é opcional — o revisor lê o
que importa — e o linter só exige revisionNotes quando projection/relation mudam
após execução; relationSampled só em K2. excludes e locators pertencem à projeção
compartilhada, não à MR — os motivos são sempre os mesmos ("CDN", "moeda", "ordem"), e o
que quebra com o redesign é a projeção.
// mr/A4-particao-categorias.mr.ts
export default defineMR({
id: 'A4-particao-categorias',
molde: { mrip: 'partition-attribute', mrop: 'complete', tree: 'Sets/CompletePartitions' },
owner: 'catalogo', // quem responde pela quebra; seleção por módulo (ADR-09)
costClass: 'K2', // K0 | K1 | K2
target: { // R(·) envelhece com a app tanto quanto N4 com a data
app: 'vendure', core: '3.7.2', storefront: 'vendurehq/nextjs-starter-vendure@7d06ae0',
seed: 'products.csv@<sha>',
},
anchoredOn: [ // requisito de origem — obrigatório (princípio 2)
'packages/core/mock-data/data-sources/products.csv',
'initial-data.ts:89-165',
], // ou 'observed', com peso menor (SaaS de terceiro, K0 em produção)
preconditions: [
{ kind: 'seed-invariant', check: 'cada produto tem exatamente 1 faceta category de topo' },
{ kind: 'no-promotion-active' },
],
projection: R_grid, // referência nomeada + versão; enumera até a última página;
// excludes e locators (com risco) vivem em R_grid, não aqui
source: { flow: 'search', params: {} },
followers: { fanOut: 'top-level-collections', sample: { k: 2, rotate: 'nightly' } }, // k+1 rotativo
relation: (f, segs) => union(segs).equals(f) && sum(segs, s => s.size) === f.size,
relationSampled: (f, segs) => union(segs).subsetOf(f) && sum(segs, s => s.size) <= f.size,
falsifiedBy: [ // análise de mutação — EXECUTADA na promoção; listar não basta
{ mutant: 'descartar a última página na enumeração', tool: 'runner-flag', violated: true },
{ mutant: 'produto sem faceta de topo no seed', tool: 'seed-patch', violated: true },
// ATENÇÃO: "{or: id} item a item" é mutante EQUIVALENTE no Vendure (§ 10.3);
// o mutante real é facetValueFilters: [{ or: facetValueIds }]
],
setupErrors: ['contador ≠ lista', 'paginação > 50 páginas'], // o que NÃO é violação
exercised: { sometimes: true }, // registro de exercício: MR morta não passa em silêncio
revisionNotes: [], // obrigatório preencher se projection/relation/excludes mudar após execução
});
Regras do formato:
anchoredOnefalsifiedBysão obrigatórios; a promoção (§ 6) recusa MR sem eles e recusafalsifiedBynão executado — um mutante pode ser equivalente (o exemplo acima carrega um, comentado) e só a execução revela.anchoredOn: 'observed'é admitido — para app sem código, seed ou schema — com peso menor, como Zhou et al. (TSE 2016) em motores de busca.- Instância vácua por inclusão. MR-A1 com B ⊂ A (Electronics ∧ Computers) não viola
o mutante OR real (20 ⊆ 20); só B ⊄ A viola (Computers ∧ Photo: 11 → 20). Pré-condição
B ⊄ A no seedem MR-A1; o mesmo risco em MR-A4 com subcoleções. OfalsifiedByexecutado é o que pega isso. - Locators estruturais são declarados com risco na projeção:
remove: { by: 'structural', selector: 'form button:has(svg.lucide-x)', risk: 'classe de ícone', fallback: 'PR aria-label no starter' }. Quando o ícone mudar, oOracleSetupErrordiz "R_cart v1, 5 dependentes" — e o revisor sabe de antemão. relationSampledexiste para toda MR K2: a versão⊆/≤que continua correta sob amostragemk+1.excludesé dicionário com motivo. Excluir "o que deu diferente" sem motivo é o oráculo que não pode falhar.- Mudar
projection,relationouexcludesdepois de uma execução exigerevisionNotes— é o que o revisor de PR lê primeiro. - MR-B2 × MR-C4 (idempotente × one-shot), MR-A5 e MR-C8 carregam em
preconditionso requisito documentado que as torna MR. MR-B1 exigei ∉ S; MR-B4 exigep ∉ carrinho ∧ estoque ≥ 1; MR-A3 exige filtros independentes e co-disponíveis e estado acumulado na UI (C2 § 4). A pré-condição nomeia o componente, não "a app": MR-A5 vale para caixa, não para espaços — a normalização diverge entre dois deploys da mesma app (§ 9.2).
5 · Projeção R(·) — as regras
A decisão de engenharia mais cara do projeto (estimativa: ~35–40% do esforço do harness; medido por MR no § 10.3). Extraído de doze instâncias e da literatura de abstração de estado de GUI (Genie, Odin, Yandrapally 2020, Liu 2026), que fez isso para inferência de modelo.
5.1 O que entra. Chave de identidade estável por item (slug, SKU, código), lida de atributo; cardinalidade explícita, separada do conjunto; grandezas de negócio na forma canônica (número, não string; subtotal antes de desconto quando a MR é de ordem); o estado dos controles que definem o resultado (facetas, sort, variante).
5.2 O que sai. Preço com moeda/formatação, imagens e srcSet, contadores "(n)" de
faceta, ordem (salvo quando a MR é sobre ordem), timestamps, ids de sessão e de pedido em
AddingItems, blocos de recomendação, texto de estoque antes de fixar variante,
breadcrumb quando reflete caminho. Tudo que a app tem permissão de requisito para variar
entre fonte e seguidor sai; na dúvida, sai até a doc dizer o contrário — e a exclusão
leva motivo.
5.3 Enumeração completa. Projeção de conjunto enumera até a última página com condição
de parada por locator; guarda de laço e contador ≠ itens são OracleSetupError, nunca
violação. Custo real de K2 é em page loads (MR-A4 no Vendure: 4 navegações, 11 loads).
Virtualização sem critério de parada observável torna a MR ❌.
5.4 Locator tipado × ARIA snapshot × rede.
| Via | Quando | Risco |
|---|---|---|
getByRole / getByLabel / getByTestId |
conjunto enumerável com chave em atributo; grandezas; estado de controle. Padrão. | renomeação de rótulo — referenciar a chave i18n |
ariaSnapshot() + poda explícita (roles permitidos, atributos removidos, { boxes } se geometria importa) |
estrutura pequena e regular | sem filtro vira "DOM inteiro" com outro nome |
| Resposta de API interceptada | só cross-check | mesmo caminho de renderização dos dois lados: bug no resolver move fonte e seguidor juntos |
5.5 O que "fixado antes" significa. O que fica fixado fora do sistema é a
relação: operador e direção vêm do molde, não do modelo; a lista de observáveis, as
guardas e as exclusões ficam em anchoredOn antes da primeira execução (o argumento —
MR necessária, não suficiente — está no C2 § 6.5). Regra operacional: três guardas
baratas — observável obrigatório não vazio (rejeitar []/"" como pass); not_equal só
com valor esperado declarado; observáveis invariantes separados dos transformados,
com normalização (caixa, 7.1M) antes de comparar — e a guarda cara, que é a que vale:
um mutante por MR.
5.6 Projeções compartilhadas (R_grid, R_cart, R_pdp, R_orders) são módulos com
versão, lista de dependentes, excludes com motivo e locators com risco. É onde o
custo da MR está e onde ele se amortiza: medido na app de referência (§ 10.3), ~40 min
para ler o starter e escrever R_grid, depois 2 min por MR que a reutiliza; a asserção
convencional custa 20–30 min cada e não reutiliza nada.
5.7 Dois estados por dois segundos. Depois de uma server action o storefront serve
o estado antigo por ~1–2 s; R(·) lê o estado assentado — é o passo 3b do § 3.
6 · Geração do critério (camada 4)
O princípio: o modelo reconhece, não inventa; nada entra no catálogo sem passar por execução e por mutante. O que se copia e o que não se copia dos artefatos existentes está marcado. A linha de base sem LLM é o MRG (Segura et al., 2022): dada uma spec de parâmetros de consulta, instanciar MRPs é barato — o valor do LLM está em descobrir a spec da tela, não em instanciar.
| # | Passo | Decisão | De onde vem |
|---|---|---|---|
| 1 | Biblioteca de moldes — os 24 do C2 § 4 como dado versionado: MRIP, MROP, direção do operador, pré-condições, exemplo | Molde é dado, não código | metamorph: família travada no prompt (copiar) |
| 2 | Inventário da tela — ariaSnapshot({ mode: 'ai', boxes: true }) filtrado por roles promovíveis, com ref e caixa no viewport, rotulado E1…En; screenshot anotado só como apoio |
O modelo recebe lista de itens, não árvore | metamorph: Set-of-Marks, element_id só do inventário (copiar) |
| 3 | Instanciação restrita — para cada molde aplicável, pergunta de reconhecimento: quais controles são filtros, qual região é o conjunto, qual locator é o total. One-shot por família, saída Zod | O modelo preenche lacunas; não escreve a relação | metamorph (copiar); MRG como referência |
| 4 | Especificação de R(·) antes de executar — observáveis com binding tipado a partir do inventário do estado inicial e do molde, não do estado final da fonte; direção do operador do molde |
Divergência deliberada do metamorph | não copiar |
| 5 | Baseline — a candidata roda contra a versão sabidamente correta com o seed padrão. Violou → descartada. Passou com observável vazio → descartada | Protocolo-padrão de avaliação por mutação (Zahid o faz à mão) | ausente no metamorph |
| 6 | Falsificação — falsifiedBy executado com ferramenta de mutação onde há código (Stryker para o starter TS; patch de seed; flag no core); ao menos um mutante tem de violar |
Resposta à MR que não pode falhar | Zahid (falhas manuais em E2E; Stryker só na API dele); Xiong 2026 (refinar por feedback) |
| 7 | Registro de exercício — contador por MR avaliada (EMF) | always/sometimes do Bombadil/Antithesis |
parcial no metamorph |
| 8 | Promoção — .mr.ts no formato do § 4, PR revisado. Sem LLM daqui em diante |
hitl por padrão; auto só com 5 e 6 verdes |
metamorph: mr_version + hash de bundle (copiar) |
O que o LLM não faria. Descobrir que Computers ⊂ Electronics torna a instância vácua;
que {or: id} item a item é equivalente; que o carrinho tem 2 s de cache. Na app de
referência (§ 10.3) essas três coisas custaram 80% do tempo de autoria e só saíram de
execução + código. A Fase 5 fica menor: o LLM instancia MRs sobre projeções
compartilhadas já escritas à mão — economiza os 2 minutos do arquivo, não os 40 da
ancoragem. O time escreve R(·).
Juiz de progresso de exploração. O único uso de modelo sobre tela que a stack aceita
é decidir se continuar explorando (metamorph explore-verify) — nunca o veredito.
Custo variável de LLM. Só aqui. Ordem de grandeza (N2): ≈ 34 chamadas, US$ 0,05 e 10 min de parede por MR compilada; 63% de compilação; MetaFOE indica que metade das instâncias pode cair mesmo com verificação por execução — orçar o dobro. Transporte da descoberta: MCP com os Test Agents oficiais.
7 · Arquitetura AWS
7.1 As dezesseis ADRs
A tabela do § 2 é o índice. Cada ADR abaixo tem três parágrafos: Decisão, Rejeitadas, Evidência. Onde a evidência é doc oficial da AWS, a citação literal e a URL estão nos registros apontados no § 0; onde é medição, o número está no § 10.3.
ADR-01 · A task do par — com a app dentro
Decisão. O sidecar Postgres escuta em localhost; logo a app que fala com ele roda
na mesma task. Vendure é server + worker; o storefront Next.js faz SSR. Task de K1/K2
= runner + Chromium + Postgres + vendure-server + vendure-worker + storefront (5–6 dos
10 containers permitidos), dependsOn postgres → (init K2) → server → storefront →
runner, com health checks. Forma inicial 4 vCPU / 8 GB arm64 (≈ US$ 0,0092/par); 2
vCPU/4 GB é irrealista com SSR + API + 2 contexts. Imagem do runner só-Chromium no ECR
com SOCI v2 (o digest muda após o índice — a task definition aponta para o digest
pós-soci convert).
Rejeitadas. Device Farm; ECS/EC2 (só vence acima de ~65% de ocupação); Managed
Instances (sem ipcMode nem tmpfs); Lambda para K1/K2; app fora da task
(reintroduz control plane e latência assimétrica).
Evidência. Doc do ECS (containers por task, localhost entre containers em awsvpc,
combinações vCPU/memória do Fargate, SOCI v2 e mudança de digest); doc de deploy do
Vendure (server e worker na mesma imagem). A forma da task é estimativa até o
experimento do § 12.
ADR-02 · Memória compartilhada — Chromium e Postgres
Decisão. Fargate não dá ipcMode/sharedMemorySize/privileged.
--disable-dev-shm-usage já é default do Playwright. linuxParameters.tmpfs em
/dev/shm dos dois containers — suportado em Fargate desde 06/01/2026 (anúncio + API
ref; o guia está atrasado); o Postgres com workers paralelos falha sem isso ("could not
resize shared memory segment"); fallback dynamic_shared_memory_type=mmap. tmpfs conta
contra a memória da task. Crash de renderer = ruído (passo 0).
Rejeitadas. --ipc=host via EC2; ignorar o modo de falha.
Evidência. Anúncio da AWS de 06/01/2026 e referência da API (LinuxParameters); guia
do desenvolvedor ainda com a restrição em 27/08/2026; chromiumSwitches.ts do Playwright;
página oficial do Postgres no Docker Hub (shm_size). Validação em Fargate e via CDK:
§ 12.
ADR-03 · Estado K1 — sidecar
Decisão. postgres:18 com PGDATA explícito fora de /var/lib/postgresql (o
VOLUME de 18 mudou e o PGDATA padrão fica dentro dele); ownership e 0700 assados no
build (o entrypoint faz chown recursivo a cada start — segundos em 500 MB); initdb
pulado por PG_VERSION; listen_addresses=localhost e SG da task sem inbound — o
entrypoint oficial escreve host all all all trust, que é qualquer host, não localhost;
shared_buffers 128 MB; buildar a imagem arm64 em Graviton, não por QEMU (PGDATA é
gerado na arquitetura alvo).
Rejeitadas. Aurora clone por par (15 CoW / 40 clusters / dois passos / acorda o
writer); Neon; Testcontainers (sem daemon); bind mount de task (root:root); trust em
all.
Evidência. docker-entrypoint.sh oficial (host all all all trust, chown
recursivo, pulo do initdb por PG_VERSION); Docker Hub (VOLUME e PGDATA da imagem
18); doc do Aurora (limites de clone e writer acordado). Custo do PGDATA no pull e do
chown no start: § 12.
ADR-04 · Estado K2 e shrinking — database por par
Decisão. CREATE DATABASE … TEMPLATE (WAL_LOG) num cluster Aurora Serverless v2
PostgreSQL 17.x (≥ 17.4) — 18.x existe no Aurora mas não consta na lista do
Serverless v2; confirmar com aws rds describe-orderable-db-instance-options --engine
aurora-postgresql --db-instance-class db.serverless. Min ACU 4 durante a rodada, 0
fora (mudar o mínimo não exige reboot; auto-pause fora evita ≈ US$ 44/mês ociosos); máx
fixado uma vez (mudar exige reboot para max_connections). Com min 0/0,5 o
max_connections é capado em 2.000. Criado por init container com dependsOn:
SUCCESS antes de o server subir; DROP DATABASE WITH (FORCE) ao fim; Lambda reaper
para pair_% com mais de 1 h. Credenciais via Secrets Manager. Standard × I/O-Optimized
decidido por VolumeWriteIOPs medido (WAL_LOG de 500 MB ≈ 128k blocos ≈ US$ 0,026/par de
I/O no Standard — 30× a ACU).
Rejeitadas. Sidecar para K2; auto-pause na rodada; PG 18 sem verificação; IAM DB auth (300–1000 MiB extra, inviável em ACU baixo); RDS Proxy (impede auto-pause).
Evidência. Página de versões suportadas do Serverless v2 (17.4+, sem coluna 18);
doc de capacidade (max_connections capado em 2.000 com min 0/0,5; reboot ao mudar o
máx); doc do PostgreSQL 18 para CREATE DATABASE (WAL_LOG bloco a bloco; template
bloqueado por conexão); preço de I/O do Aurora. A conta de I/O é estimativa —
VolumeWriteIOPs sob 20 criações concorrentes é experimento do § 12.
ADR-05 · Orquestração
Decisão. Step Functions Standard + Distributed Map, filhos Standard, entrada por
ponteiro S3 (256 KiB por estado), ResultWriter para S3. O par nunca cruza um
estado. Contrato: ecs:runTask.sync falha sempre como States.TaskFailed; exit ≠ 0 =
ruído → Retry (1–2×, backoff, jitter); RunTask sob cota vem como AmazonECS.Unknown
→ Retry; ToleratedFailurePercentage explícito (padrão é zero); TimeoutSeconds ≈ 3×
o par; idempotência por chave S3 (runId, mrId, attempt). Semáforo de rodadas (fila
SQS ou MaxConcurrencyPath lido de contador) — 20 rodadas/dia se sobrepõem e duas Map
Runs a cota/2 estouram a cota. MaxConcurrency ≤ 100 enquanto o burst launch rate for
100. Custo: US$ 0,000025/transição ≈ US$ 0,0003/par.
Rejeitadas. Express (5 min); Map inline (40); dividir o par; rodadas sem semáforo.
Batch em Fargate fica como alternativa condicional: enfileira sob cota e faz retry
por exit code de graça, mas não dá tmpfs — se ADR-02 for necessário, Batch cai;
Batch sobre EC2/Spot para o noturno K2.
Evidência. Cotas do Step Functions (256 KiB; 100 execuções/s no Distributed Map);
doc da integração com ECS (Failures → AmazonECS.Unknown; cancelamento best-effort
do .sync); ToleratedFailurePercentage padrão zero; burst launch rate 100 do Fargate.
Que o erro do .sync é sempre States.TaskFailed é N2 (README de terceiros) — o
Cause sob cota estourada é experimento do § 12.
ADR-06 · K0 e monitoramento
Decisão. K0 em Lambda no pré-deploy (imagem até 10 GB, 15 min, EphemeralStorage
2 GB, HOME/XDG_* em /tmp) e em CloudWatch Synthetics em produção (3.008 MB,
840 s, EphemeralStorage 1–10 GB). Adaptador runPair(browser, mr) puro com dois
entrypoints — o runtime do Synthetics envolve o Playwright em
@aws/synthetics-playwright, então "mesma versão" ≠ "mesmo código". K0 é workspace
separado fixado na versão do runtime (syn-nodejs-playwright-8.0 = 1.61.1 hoje); job
semanal com dono lê DescribeRuntimeVersions e abre PR de bump — a cadência é ~um
runtime por mês e sem dono o pin apodrece.
Rejeitadas. Checkly (~10× o Synthetics — números no C3 § 3.6; dados fora da conta); Fargate para K0 (frio de 30–60 s para 60 s de trabalho).
Evidência. Doc do runtime Synthetics (syn-nodejs-playwright-8.0 = Playwright
1.61.1; cadência 5.0 → 8.0 em ~8 meses); CanaryRunConfigInput (3.008 MB, 840 s,
EphemeralStorage); limites do Lambda (10 GB, 900 s, /tmp 512 MB–10 GB). Preço arm64
do Lambda e US$ 0,0012/run do Synthetics são N2 (Price List API, não a página de preço).
Se DescribeRuntimeVersions expõe a versão do Playwright: § 12.
ADR-07 · Capacidade e cotas
Decisão. Sem Fargate Spot para pares (aviso de 2 min, stopTimeout ≤ 120); Spot
arm64 só para shrinking offline e noturno em Batch/EC2. Todos os tickets no dia 1 em
conta nova: L-3032A538 (Fargate On-Demand vCPU, inicial 6 = 3 tasks de 2 vCPU; ≥ 400
para 100 tasks de 4 vCPU), Step Functions ("reduced state transition quotas"), Lambda
("reduced concurrency and memory quotas"), ENIs/IPs (100 tasks = 100 ENIs → subnets
/24 no mínimo), RDS 40 clusters/instâncias.
Rejeitadas. Spot para tudo; esperar a cota subir sozinha.
Evidência. Cotas do ECS/Fargate (L-3032A538 = 6 em conta nova; burst 100/s,
sustained 20/s); doc de capacity providers (aviso de 2 min como SIGTERM, stopTimeout
máx 120 s); notas de cota reduzida em conta nova para Step Functions e Lambda; cotas do
RDS (40 clusters, 40 instâncias). Spot arm64 no Fargate existe desde 09/2024.
ADR-08 · Artefatos e contrato do runner
Decisão. S3, trace: 'retain-on-failure', Expiration 30 dias, sem transição;
layout runs/<runId>/<mrId>/<attempt>/{verdict.json,projections/,traces/};
verdict.json sempre, trace e replayPath só em violação; exit 0 = rodou; bucket
criptografado, acesso restrito — trace + storageState é sessão reproduzível. Trace
típico 1–10 MB.
Rejeitadas. trace: 'on'; Glacier IR; traces legíveis por qualquer role.
Evidência. O contrato de exit code decorre do comportamento do .sync (ADR-05); o
tamanho de trace é ordem de grandeza — a medida nas MRs do Vendure é experimento do § 12.
ADR-09 · Seleção, ruído e triagem
Decisão. K0 sempre; K1 por módulo tocado (owner); K2 noturno com k+1
rotativo. Antes de qualquer LLM: retryStrategy: 'isolated', histórico da MR (par
como unidade), e controle de ruído fonte×fonte na mesma task — +1 execução por MR
por build; violação só quando fonte×seguidor excede fonte×fonte. O limiar do passo 0
sai dessa medição, não de chute. Tags de custo (run-id, cost-class, owner, mr-id)
com propagateTags; Budgets por tag; EMF por rodada.
Rejeitadas. Rodar tudo em todo PR; quarentena automática; LLM como primeiro classificador; Container Insights por padrão.
Evidência. Na app de referência (§ 10.3): ruído fonte×fonte 0/7, e o que derrubou
MR-B1 não era ruído — era estado não assentado, que o controle fonte×fonte não detecta
(daí o passo 3b do § 3). propagateTags e enableECSManagedTags na referência do
RunTask. O limiar do passo 0 é experimento do § 12.
ADR-10 · Versões
Decisão. Playwright fixado por tag de imagem e digest; não subir sem medir memória
(regressão em 1.57, parcialmente resolvida em 1.58). Task definitions são imutáveis
(rollback = revisão anterior); state machine com versions/aliases; template do Aurora
nomeado tpl_v<sha>; imagem do sidecar e template mudam juntos quando products.csv
muda.
Rejeitadas. latest.
Evidência. Releases do Playwright (1.62.1 é a última em 27/08/2026; regressão de memória em 1.57); doc do Step Functions (versions/aliases) e do ECS (revisões imutáveis de task definition).
ADR-11 · Determinismo
Decisão. page.clock.install(T0) antes de qualquer chamada de clock, nos dois
contexts; routeFromHAR só para assets e terceiros, nunca para a API da app;
serviceWorkers: 'block' declarado na MR (muda a app sob teste, e o service worker
derrota o HAR em silêncio); storageState por sujeito (A, B, anônimo) e credentials
para passkey; clipboard e viewport fixos. Tudo in-process, em microssegundos — é o passo
2a do § 3; a parte cara do determinismo é o estado (ADR-03, ADR-04).
Rejeitadas. Gravar a API no HAR (a MR passaria a comparar HAR com HAR, e a app sob
teste sairia do laço); waitForTimeout como espera por estado (o assentamento é o
passo 3b, com duas leituras iguais como critério observável); resolver determinismo por
infraestrutura o que se resolve no processo.
Evidência. § 3 (passo 2a) e § 7.4 item 3; capacidades do Playwright 1.62 (clock,
routeFromHAR, serviceWorkers, storageState) em C3 § 3.1; na app de referência
(§ 10.3), o falso positivo não veio de clock nem de rede, mas de estado não assentado.
ADR-12 · Motor de relação
Decisão. fast-check 4.9 gera sequências de ações e fixtures (entityGraph) e,
para MR-B2, scheduledModelRun; o shrinking roda offline, sob demanda, com
orçamento, relendo seed + replayPath (§ 3); a relação é código próprio — função
pura sobre duas projeções, com o contrato do § 8. Bombadil é avaliado como motor
das MRs K0 de sessão única (P ∩ D; C2 § 7.1) — o experimento é da Fase 2 (§ 12).
Rejeitadas. Chamar fast-check de "motor de relação" (é gerador; a relação não vem dele — C3 § 3.3); shrinking no CI (exige estado reconstruível e orçamento próprio — ADR-04); gemtest (C3 § 3.3, § 13).
Evidência. § 3 (o registro não encolhe; shrinking offline) e § 8 (projeção → relação); C2 § 7.1 (Bombadil expressa toda MR de sessão única, mas não faz par em contexts separados, baseline nem falsificação).
ADR-13 · Geração e agente
Decisão. O pipeline do § 6: molde travado → LLM instancia com saída Zod → baseline →
falsificação por mutante executado → promoção, hitl por padrão. O LLM instancia MRs
sobre projeções compartilhadas escritas à mão; o único uso de modelo em runtime é o juiz
de progresso de exploração. Agente: Test Agents oficiais planner + generator em
dev-time, transporte MCP; healer removido; Stagehand só para descoberta em runtime.
Rejeitadas. Família F (LLM como oráculo); R(·) escolhida após execução (o oráculo
que não pode falhar); healer, Runtime Recovery e cache de agente (princípio 4);
extract em R(·).
Evidência. § 6 (passos 1–8; o que se copia e o que não se copia do metamorph); na app de referência (§ 10.3), o que custou 80% da autoria só saiu de execução + código; transporte MCP verificado no código dos Test Agents (C3 § 4); a regra "o veredito é código" (C2 § 7.3).
ADR-14 · Projeção
Decisão. Locator tipado (getByRole/getByLabel/getByTestId) como padrão; ARIA
snapshot só com filtro explícito (roles permitidos, atributos removidos); rede só
como cross-check; varredura implícita (console, 5xx) em toda execução; projeções
compartilhadas versionadas, com excludes com motivo e locators com risco (§ 5.6);
herda a literatura de abstração de estado de GUI (§ 5).
Rejeitadas. Screenshot como R(·) (compara pixels, não estado); extract de agente
(julgamento por modelo dentro da projeção); resposta de API como projeção primária
(mesmo caminho de renderização dos dois lados — bug no resolver move fonte e seguidor
juntos — e muda o objeto testado).
Evidência. § 5.1–5.4 (regras extraídas de doze instâncias); R_grid e R_cart
executadas contra demo e instância local (§ 9.2, § 10.3); C2 § 7.2 (evidência para
locator tipado).
ADR-15 · Rede
Decisão. Subnets privadas + VPC endpoints ecr.api, ecr.dkr, logs,
secretsmanager (US$ 0,01/h por endpoint por AZ + US$ 0,01/GB) + S3 gateway endpoint
(obrigatório para as camadas do ECR, sem custo de dados) ≈ US$ 44–58/mês fixos; SG
de egress restrito aos endpoints e à app; sem NAT. Se a app precisar de internet,
terceiros já vão por HAR.
Rejeitadas. NAT Gateway (905 MiB × 475 tasks = 451 GB/rodada × US$ 0,045 = US$ 20/rodada ≈ US$ 8.900/mês, 4–9× o compute); IP público no runner (ECR grátis na região, mas sessão autenticada num ENI público).
Evidência. Preços de VPC, PrivateLink e ECR lidos em 27/08/2026; doc do ECR sobre o S3 gateway obrigatório. Bytes reais por rota, ±SOCI: § 12.
ADR-16 · Segurança, IAM, contas
Decisão. Quatro roles: state machine (ecs:RunTask na task definition específica,
StopTask/DescribeTasks, events:PutRule, iam:PassRole, states:StartExecution
própria, S3 do manifesto/ResultWriter), task execution (ECR, logs, secrets), task role
(S3 artefatos, EMF, secrets), Lambda de triagem (S3 read, LLM). ECR scan on push e
tag immutability; logs com retenção por ambiente. Contas: CI (Fargate + Aurora de
teste + ECR) em tooling; K0 pré-deploy em staging; Synthetics em produção
(artefatos ficam lá); replicação de ECR entre contas. IaC: CDK (ASL tipado, task
definitions, soci no pipeline, Synthetics Runtime como construct).
Rejeitadas. trust alcançável na VPC; uma conta para tudo; IaC ad hoc.
Evidência. Templates de política na doc do Step Functions (integração ECS,
Distributed Map — StopTask com Resource: "*" porque o TaskId só existe depois do
submit); recomendação de tags imutáveis na doc do ECS. O risco "trace + storageState =
sessão reproduzível" é raciocínio próprio (§ 11).
7.2 Fluxo de uma rodada
EventBridge / webhook de PR
│
▼
Semáforo de rodadas (SQS) ── uma Map Run por vez, ou MaxConcurrency dinâmico
│
▼
Step Functions (Standard) — pai
├─ 1. Seleciona MRs: classe × owner × orçamento → manifesto em S3
├─ 2. Aquecimento: min ACU → 4 no cluster Aurora (K2); confirma cota
├─ 3. Distributed Map (filhos Standard, MaxConcurrency ≤ min(100, cota/4),
│ entrada = ponteiro S3, TimeoutSeconds ≈ 3×par, Retry em TaskFailed/ECS.Unknown,
│ ToleratedFailurePercentage explícito)
│ ▼
│ ecs:runTask.sync → UMA task Fargate arm64 4 vCPU/8 GB por MR
│ ├── init (K2): CREATE DATABASE pair_<id> TEMPLATE tpl_v<sha> → SUCCESS
│ ├── postgres:18 (K1): PGDATA semeado, tmpfs em /dev/shm, localhost → HEALTHY
│ ├── vendure-server · vendure-worker (DB_HOST=localhost | cluster) → HEALTHY
│ ├── storefront Next.js → HEALTHY
│ └── runner Playwright (tmpfs em /dev/shm)
│ ├─ clock.install(T0) · routeFromHAR(assets) · serviceWorkers:'block' · storageState
│ ├─ context 1 = FONTE ─┐ mesmo processo, mesmo clock, mesmo banco,
│ ├─ context 2 = SEGUIDOR ┘ um test(), test.step() por fase
│ ├─ (por build) fonte×fonte na mesma task → piso de ruído
│ ├─ R(fonte), R(seguidor) — OracleSetupError separado de violação
│ ├─ passo 0: crash? delta > limiar calibrado? → RUÍDO, exit ≠ 0
│ ├─ relação, determinística · EMF mr.evaluated += 1
│ └─ verdict.json → S3; violação: + seed + replayPath + trace.zip; exit 0
├─ 4. ResultWriter → S3 · DROP DATABASE dos pares K2 (reaper cobre o resto)
└─ 5. Violações: histórico → ruído fonte×fonte → Lambda com LLM {bug, MR, ruído}
Fora do laço: K0 em Lambda (pré-deploy) e Synthetics (produção), adaptador runPair,
versão do runtime · shrinking sob demanda relendo seed + replayPath
7.3 Custo — ordem de grandeza (us-east-1, 27/08/2026, aritmética mostrada)
Premissas: par = 60 s de frio + 150 s de trabalho = 210 s; task 4 vCPU / 8 GB arm64 (app dentro); ~12 transições; 2% de violação; trace 5 MB; rede por endpoints.
| Item | Por par | Conta |
|---|---|---|
| Fargate arm64 4 vCPU / 8 GB, 210 s | US$ 0,0092 | (0,0000089944×4 + 0,0000009889×8) × 210 |
| Fargate arm64 2 vCPU / 4 GB (só o runner — referência) | US$ 0,0046 | (0,0000089944×2 + 0,0000009889×4) × 210 |
| Step Functions, ~12 transições | US$ 0,0003 | 12 × 0,000025 |
S3 com retain-on-failure |
≈ US$ 0,00002 | 2 PUT + 5 MB × 2% × 30 d |
| Rede (endpoints, rateado) | ≈ US$ 0,0002 | US$ 50/mês ÷ (475 × 440 rodadas) |
| Par K1, total | ≈ US$ 0,0097 | |
| Par K2: + database no Aurora (4 ACU compartilhados por 100 pares/10 min) | + ≈ US$ 0,0008 de ACU + até US$ 0,026 de I/O no Standard (500 MB WAL_LOG) — medir; I/O-Optimized zera | 0,48/6/100 |
| Par K0 em Lambda arm64, 3 GB, 60 s | US$ 0,0024 | 60 × 2,94 × 0,0000133334 (Price List API) |
| Canário K0 no Synthetics, 60 s | US$ 0,0041 | 0,0012 (Price List API) + Lambda x86 |
| Escala | Custo | Wall clock |
|---|---|---|
| Rodada de 475 execuções, Fargate arm64 4 vCPU | ≈ US$ 4,6 | com cota de 6 vCPU (1 task de 4): 475 × 210 s = 27,7 h · com 400 vCPU (100 tasks): 475 × 210 / 100 ≈ 17 min |
| 20 rodadas/dia × 22 dias | ≈ US$ 2.000/mês de compute + US$ 50 de rede | — |
| Mesma carga via NAT em vez de endpoints | + US$ 8.900/mês | — |
| Aurora ocioso com min 0,5 ACU (se não desligar fora da rodada) | + US$ 44/mês | — |
| 20 MRs K0 a cada 15 min em produção, Synthetics | ≈ US$ 238/mês | Checkly: ~10× (C3 § 3.6) |
Artefatos, retain-on-failure |
< US$ 1,50/mês | sem: US$ 13–65/mês |
A conclusão de custo: o compute é barato; o que decide o orçamento é a rede (uma
ADR) e a cota (tempo) — e, para K2, o I/O do CREATE DATABASE, que só a medição
fecha.
7.4 Armadilhas documentadas
- Cota inicial de 6 vCPUs = uma task de 4 vCPU. Todos os tickets no dia 1.
- Fargate não cacheia imagem: 905 MiB (runner) + Postgres + app puxados por task. SOCI v2 corta ~50% (AWS); imagem só-Chromium corta mais; a rota dos bytes decide o custo (ADR-15).
- Service worker da app derrota
routeFromHARem silêncio.serviceWorkers: 'block'é obrigatório e muda a app sob teste — declarado na MR. - Auto-pause do Aurora: resume de 15–30 s assimétrico. Min ACU 4 na rodada.
- Spot no meio de um par = meio par.
VOLUMEda imagem Postgres,chownrecursivo no start,trustemall,listen_addresses='*'— quatro armadilhas na imagem oficial (ADR-03).- Template bloqueado:
CREATE DATABASEfalha se houver conexão ao template. O template nunca recebe a app. Databases órfãos sem reaper degradam o catálogo. - O passo 0 mede um ruído que o próprio harness fabrica quando Chromium, SSR e API competem na mesma task — o limiar vem de fonte×fonte na mesma task, nunca de chute.
- Chromium 1.57+ teve regressão de memória; 706 MB de pico por contexto (pré-1.57) pode estar subestimado.
- Rodadas concorrentes disputam a cota e derrubam Map Runs com tolerância zero.
8 · Interfaces entre camadas
Contratos mínimos para duas pessoas — harness e plataforma — trabalharem em paralelo.
| De → para | Contrato |
|---|---|
| Molde → gerador | JSON: { id, mrip, mrop, operatorDirection, preconditions[], example } |
| Gerador → validação | MRCandidate = defineMR sem revisionNotes, com anchoredOn e falsifiedBy; rejeitada se vazios |
| Validação → catálogo | MRCandidate + { baselineRun: pass, falsificationRuns: [{mutant, violated: true}], exercised: n } → .mr.ts |
| Seleção → orquestração | manifesto S3 versionado: [{ mrId, costClass, owner, taskShape, followers[], dbMode }]; schema de entrada da state machine e do ItemReader |
| Orquestração → runner | env: MR_ID, MANIFEST_S3, RUN_ID, ATTEMPT, DB_MODE=sidecar\|template, DB_HOST/PORT/NAME, DB_SECRET_ARN, APP_URL, T0, STORAGE_STATE_S3[], ARTIFACT_BUCKET, EMF_NAMESPACE |
| Task | dependsOn e health checks: postgres HEALTHY → init SUCCESS (K2) → server HEALTHY → storefront HEALTHY → runner; quem cria o database é o init container |
| Runner → projeção | Page + ProjectionSpec → Projection (JSON, com cardinality) ou OracleSetupError |
| Projeção → relação | (Projection, Projection[]) → { verdict: pass\|fail, observables: [...] } — função pura |
| Runner → artefatos | exit 0 = rodou; runs/<runId>/<mrId>/<attempt>/verdict.json sempre; projections/, traces/, replayPath em violação; nunca payload em Step Functions |
| Runner (por build) → ruído | runs/<runId>/<mrId>/noise.json = divergência fonte×fonte |
| Artefatos → triagem | histórico por mrId → controle de ruído → LLMTriage({ mr, projections, traces, noise }) → { class, rationale } |
| Reaper | Lambda horária: DROP DATABASE WITH (FORCE) de pair_% com datcreated < now() − 1h |
| K0 | runPair(browser, mr) puro; entrypoints lambda.ts e synthetics.ts; workspace k0/ fixado no runtime |
9 · Aplicação de referência
9.1 Perfil (checklist de aceite para qualquer app-alvo)
Funcional: RF1 busca por texto acionável por URL · RF2 filtros facetados com semântica declarada · RF3 ordenação ≥ 2 critérios em URL · RF4 paginação enumerável com sinal de última página · RF5 alternância de apresentação (ou substituto) · RF6 categorias que particionam, verificável no seed · RF7 carrinho com subtotal e total distintos · RF8 cupom com regra de reaplicação documentada · RF9 ≥ 2 papéis e ≥ 1 recurso só do dono · RF10 rotas diretas estáveis · RF11 estado por sessão · RF12 variantes com estoque do seed.
Infra: I1 Docker · I2 Postgres · I3 seed determinístico · I4 partição provável no
seed · I5 API administrativa scriptável · I6 licença · I7 sem service worker
obrigatório · I8 API versionada · I9 demo pública · I10 locators estáveis · I11 a app
cabe numa task (processos, memória, boot) · I12 controles alcançáveis por getByRole +
click() sem overlay — header fixed z-50 sobre um radio custou dezenas de cliques
falhos na primeira execução; o clique no label resolveu · sem modal bloqueante no fluxo
de busca. I4, I5, I11 e I12 são obrigatórios.
9.2 Decisão
Principal: Vendure core 3.7.2 (GPL-3.0/VCL) + vendurehq/nextjs-starter-vendure@7d06ae0
(MIT).
Por que.
- Seed com partição provada (54 = 20 + 20 + 14, confirmada em dois backends); Postgres nativo; TS ponta a ponta.
- Admin GraphQL para pré-condições — só no local: a demo pública rejeita
superadmin. - Semântica de filtro legível no código (
{and: id};?facets=3&facets=5→ "No products found"); a URL é a interface de teste (?q=,?facets=,?sort=,?page=). - Demo pública para as MRs K0.
- 12/12 MRs instanciáveis, três com ressalva: MR-A2 e MR-A6 com instância substituta, MR-A5 só caixa. MR-A1 e MR-B1 executadas contra demo e local.
Como sobe. npx @vendure/create <nome> --ci --with-storefront --db postgres —
7m49s medidos, Postgres em Docker incluso. Imagem própria só para Fargate. No harness, o
storefront roda com next build && next start — em dev o primeiro hit de cada rota
leva 2–3 s e contamina o passo 0. Três processos (server, worker, storefront) dentro da
task — I11 a medir.
Ressalvas medidas.
- Ícones sem nome. "HTML semântico" vale para cards e links; três controles são ícones
mudos sem nome acessível (remover, +/−, setas de paginação) — abrir PR no starter com
aria-labelno dia 1 ou declarar locator estrutural com risco (§ 4). - Moeda. A moeda alternativa (substituta de MR-A6) não existe no seed
(
availableCurrencyCodes: ["USD"]→ o controle não renderiza); exigeupdateChannel+ preços em EUR no script de pré-condições. - Merge de carrinho no login. Existe, e a regra está no core 3.7.2:
MergeOrdersStrategy— a quantidade do convidado substitui a da linha existente (não soma), linhas novas entram na frente; no checkout,UseGuestStrategy. MR-B5 inclui o carrinho com a pré-condiçãoguestOrder = ∅, ou vira MR-B5′ com o merge na relação (C2 § 4). - Caixa e trim. A busca é case-insensitive nos dois deploys, mas o trim diverge — a demo não normaliza espaços, o local (v3.7.2/Postgres) normaliza. MR-A5 vale para caixa; a variante com espaços é o falso positivo da primeira semana. A pré-condição nomeia o componente, não "a app".
Contras. Core GPL (uso interno ok); starter jovem.
Reserva: Saleor (BSD-3 + storefront FSL-1.1-ALv2). Sandbox de validação do harness:
Toolshop (with-bugs = mutantes gratuitos; licença proprietária, MariaDB — não é alvo).
10 · Plano de piloto
10.1 Fases de adoção
| Fase | Entrega | Infra | Responde |
|---|---|---|---|
| Fase 1 | Oráculo implícito sobre os fluxos Playwright existentes — inclui MR-C9 com q=<script> |
nenhuma | — |
| Fase 2 | MRs K0 (MR-A5 só caixa, MR-A7, MR-B3, MR-C10; MR-A3 e MR-C7 só em SPA com estado acumulado na UI) em Lambda no pré-deploy e Synthetics em produção, com R(·) que exclua ranking/personalização/promoção; avaliar Bombadil como motor destas |
Lambda + Synthetics | E9b (replay sem camada 2) para K0; divergência de versão declarada |
| Fase 3 | Camada 2 em CI → grupo A em Fargate com a app na task; MR-A4/MR-A8 amostradas | VPC + endpoints, ECR + SOCI, task de 5–6 containers, Step Functions, IAM, cotas | frio real, tmpfs, forma da task, limiar do passo 0 |
| Fase 4 | Sidecar → grupo B e MR-C1–C6, MR-C8; database por par para K2 e shrinking | + Aurora Serverless v2, template pipeline, reaper, secrets | E10 (custo de R(·)) completo; E9c com mutantes |
| Fase 5 | Geração assistida: moldes → LLM → baseline → falsificação → promoção | + Lambda de triagem, Langfuse | E9a (que fração das MRs geradas sobrevive à validação?); E12 (§ 10.3) |
10.2 Três pilotos
Três pilotos, do menor para o maior:
| Piloto | Etapas | Horas |
|---|---|---|
| MVP de 1 semana — o que convence um QA lead | @vendure/create --ci + next build + Postgres em Docker (0,5 h) · 5 MRs à mão: MR-A1 (Computers ∧ Photo), MR-A7, MR-B1, MR-B3, MR-C6 · três projeções (R_grid, R_cart, R_pdp) · runner com settle e ruído fonte×fonte · falsifiedBy executado (sed no storefront) · entregável: tabela MR × mutante × pegou? — sem AWS, sem K2, sem LLM |
~2 dias |
| Harness (local) — contém o MVP | subir app + pré-condições via Admin API (1–2 h) · camada 2: storageState, HAR de assets, sidecar (6–8 h) · 4 projeções + 12 R(·) + indícios não confirmados (PISTAs) restantes (16–24 h — MR-B2/MR-B6/grupo C exigem promoções, dois storageState e leitura do core) · harness com OracleSetupError, exit code, verdict.json, seleção por owner (4–6 h) |
27–40 h |
| Plataforma (Fases 3–4) | ADR-15 rede + endpoints · ECR + pipeline SOCI · task definition de 5–6 containers com dependsOn e init · 4 roles IAM · ASL com Retry/Timeout/semáforo · Aurora + template + reaper + secrets · CDK · tickets de cota |
24–40 h de infra + 16–24 h de experimentos (§ 12) = 40–64 h |
| Total | o MVP está contido no harness; total = harness + plataforma | 67–104 ≈ 70–100 h |
O que medir primeiro, nesta ordem: (0) os mutantes — é a única coisa que diz se a
MR pode falhar, e a app de referência entregou um equivalente e uma instância vácua no
primeiro dia (§ 10.3); (1) a task com a app dentro sobe? memória/CPU por container, tempo
até HEALTHY; (2) bytes e tempo de pull por caminho de rede, ±SOCI; (3) fonte×fonte na
mesma task para calibrar o limiar do passo 0.
10.3 O experimento que só o piloto responde — E9c e E12, com os primeiros pontos
E9c — com MR validada em E2E web, que fração das violações é bug? E12 — que bug a família D acha que Meticulous (C) e Bombadil (P) não acham, e quanto vale?
E9c — medido em miniatura na app de referência (local, sed + HMR, revertido):
| Mutante | MR | Asserção convencional |
|---|---|---|
{or: id} item a item (o mutante óbvio) |
passa — mutante equivalente | passa |
[{ or: facetValueIds }] (OR real) |
MR-A1 (Electronics ∧ Computers): não detecta — 20 ⊆ 20, instância vácua · MR-A1 (Computers ∧ Photo): viola, 11 → 20 | '11 products': detecta |
removeFromCart sem mutate |
MR-B1: viola | detecta |
Os outros falsifiedBy (última página descartada; produto órfão; cupom duplicado;
orderByCode sem dono) ficam para o piloto, com Stryker no starter e a variante
with-bugs do Toolshop. Medir: mutantes que cada MR mata; violações sem mutante; passes
vácuos. É o primeiro ponto com mutantes semeados antes das MRs — o de Zahid (10/10,
C2 § 2) tem viés de seleção declarado.
E12 — primeiro ponto, medido na app de referência (amostra de 2, N5 na
extrapolação): por cenário, MR ≈ 2× a asserção (45–60 min × 20–30 min para um QA
sênior do zero); 80–90% do custo da MR é R(·) + ancoragem; o custo marginal da
k-ésima MR sobre projeção pronta é ~2 min, e a asserção não amortiza. Ruído fonte×fonte
0/7; falsos positivos por estado não assentado 4/4 sem settle (§ 3 passo 3b). A resposta
honesta a um QA lead com suíte Playwright existente — onde a MR ganha por família de
tela, onde a asserção com valor esperado é mais curta e mais forte, e por que a MR
soma à suíte em vez de substituí-la — está no C2 § 4, "Quem escreve".
O que o piloto ainda deve a E12: para os mesmos mutantes, o que Meticulous (replay diferencial) e Bombadil (propriedade sobre trace) pegam; e o custo marginal na k-ésima MR, não "10 MRs × asserção".
11 · Riscos e modos de falha
| Risco | Efeito | Mitigação |
|---|---|---|
| App dentro da task fabrica o ruído que o passo 0 mede | falso positivo de infra | limiar calibrado por fonte×fonte na mesma task; forma 4 vCPU/8 GB |
| Egress de imagem via NAT | US$ 8.900/mês | ADR-15 |
| Rodadas concorrentes disputando cota | Map Runs falham com tolerância zero | semáforo; Retry em AmazonECS.Unknown; ToleratedFailurePercentage |
| Chromium/Postgres sem shm | crash lido como violação | tmpfs nos dois; passo 0 |
| Auto-pause / resume assimétrico | fonte quente, seguidor frio | min ACU 4 na rodada |
| Service worker derrota o HAR em silêncio | estados não equivalentes | serviceWorkers: 'block', declarado |
| Relação ajustada até passar | oráculo que não pode falhar | revisionNotes; falsifiedBy executado com Stryker |
| Estado não assentado após mutação lido como violação | falsos positivos em MR-B1 (§ 10.3) | § 3 passo 3b; rerun 7a′ |
| Mutante equivalente ou instância vácua por inclusão | falsifiedBy que não falsifica |
executar na promoção; pré-condição B ⊄ A |
| Locator estrutural (ícone sem nome) | quebra sem requisito para reancorar | declarar com risco; PR de aria-label |
| Um time de QA cortaria 15 de 24 MRs | catálogo maior que a adoção | começar pelas 9 que um QA escreve (C2 § 4, "Quem escreve") |
| Healer / Runtime Recovery / cache de agente | julgamento silencioso | removidos (ADR-13) |
| Versão do Synthetics atrás do CI | divergência CI↔produção | workspace K0 no runtime; job com dono |
Trace + storageState em S3 |
credencial reproduzível por 30 dias | bucket criptografado, acesso restrito, retenção |
Postgres trust alcançável |
banco sem senha na VPC | listen_addresses=localhost, SG sem inbound |
| PG 18 no Serverless v2 | cluster não provisiona | 17.x até verificação |
| Metade das instâncias cai na validação (MetaFOE) | custo de descoberta dobra | orçar 2× na Fase 5 |
| Licenças: Vendure GPL; Saleor FSL; metamorph sem licença; Diffy NC-ND | redistribuição | uso interno; ler, não copiar |
12 · Aberto — só se resolve medindo
| Pergunta | Experimento | Sai como |
|---|---|---|
| A task com a app dentro sobe? Forma real | 20 tasks; memória/CPU por container; tempo até HEALTHY |
ADR-01 |
| Frio real, ±SOCI, x86/arm64, por caminho de rede (endpoints × NAT × IP público) | 20 tasks × combinações; describe-tasks (pullStartedAt/pullStoppedAt/startedAt); bytes por rota |
ADR-01, ADR-15 |
| Limiar do passo 0 | fonte×fonte na mesma task, 20 pares | ADR-09 |
tmpfs valida em Fargate (e via CDK/CloudFormation)? ajuda Chromium e Postgres? /dev/shm padrão |
RegisterTaskDefinition + 20 pares ±tmpfs; workers paralelos no Postgres |
ADR-02 |
Sidecar: pg_isready, custo do PGDATA no pull, chown no start |
imagem com 50/500 MB; time + pullStoppedAt |
ADR-03 |
| Versão do Aurora Serverless v2 (18.x?) | describe-orderable-db-instance-options --db-instance-class db.serverless |
ADR-04 |
CREATE DATABASE TEMPLATE sob 20 criações concorrentes, min ACU 0,5 e 4; VolumeWriteIOPs |
psql paralelo | ADR-04 (Standard × I/O-Optimized) |
Failures do RunTask sob cota estourada; o que chega ao Cause do .sync |
provocar cota | ADR-05 (Retry) |
| Latência de clone Aurora (caso por PR) | clone em dois passos (restore + instância), tempo do control plane | referência |
Tamanho de trace.zip nas MRs do Vendure |
--trace on em 10 MRs |
ADR-08 |
Lambda + imagem Playwright arm64: 2 contexts sem --single-process? |
função 4 GB com aws-lambda-ric |
ADR-06 |
DescribeRuntimeVersions expõe a versão do Playwright? |
chamada | ADR-06 |
Os 4 falsifiedBy restantes (última página, órfão, cupom, orderByCode) |
Stryker + patches | E9c |
| Bombadil expressa as MRs K0 do catálogo com menos código que o harness? | reescrever MR-A5, MR-A7, MR-C10 em Bombadil | Fase 2, ADR-12 |
| Taxa de violação→bug com MR validada e mutantes | § 10.3 | E9c |
| Que bug a família D acha que Meticulous (C) e Bombadil (P) não acham | § 10.3 | E12 |
13 · O que não usaríamos, e por quê
- Momentic, mabl, qa.tech, testRigor, Autify como oráculo — família F.
- Healer dos Test Agents, Runtime Recovery do mabl, cache do Stagehand em runtime.
- Aurora clone por par, Neon, Testcontainers em Fargate; EFS para PGDATA (NFS sob
Postgres); RDS Proxy (impede auto-pause); Aurora Limitless (sharding, não
isolamento); DSQL (sem
CREATE DATABASE … TEMPLATE, sem as extensões do Vendure); EKS + Karpenter (dá shm e Spot, mas é um cluster para duas pessoas operarem); App Runner (serviço HTTP); EC2 Mac (só WebKit real). - NAT Gateway para o egress de imagem.
- Checkly no alvo AWS — continua certo se paridade de versão for inegociável.
- Screenshot,
extractou resposta de API comoR(·)primária. - Quickstrom como dependência (Bombadil no lugar); gemtest; Lost Pixel; browserless sem licença comercial; Skyvern/Lightpanda sem jurídico; Diffy como código.
- Fargate Spot para pares;
trace: 'on'no CI; Glacier para traces; PG 18 no Serverless v2 sem verificar;trustemall.
Evidência: pesquisa/exploracao/X4-aws-plataforma.md, X2-mstwi-metamorph.md,
X3-mercado-n4.md; asserções rastreadas A19, A26–A32 em pesquisa/protocolo.md § 4.
Registro: revisões R3 (plataforma) e R6 (execução) em pesquisa/revisoes/v2/; código de
referência das MRs executadas em R6-qa-lead.md § 5; projeções em
pesquisa/exploracao/X5-app-referencia-projecao.md.